// =======================
// SillyTavern 扩展：预设打包助手 (st-packaging-assistant)
// - 动态加载 settings.html（momo 风格）
// - 悬浮纸箱子球 FAB 可按需启用/禁用，拖拽与位置持久化
// - 面板 overlay：优先加载 overlay.html，缺省内建
// - iframe 内页集成 预设/正则/快速回复/世界书 打包导入
// =======================

jQuery(async () => {
  // ---------- 常量区 ----------
  const SPA_EXT_NAME = "st-packaging-assistant";
  const SPA_EXT_PATH = `scripts/extensions/third-party/${SPA_EXT_NAME}`;

  const STORAGE_KEY_POS = "spa-fab-pos";        // FAB 位置
  const STORAGE_KEY_ENABLED = "spa-enabled";    // FAB 启用开关（"1"|"0"，默认启用）

  const SPA_SETTINGS_MOUNT_ID = "spa-settings-mounted"; // settings.html 顶层容器 id（用于防重复）
  const SPA_OVERLAY_ID = "spa-overlay";                // overlay 容器 id

  // ---------- 启用状态工具 ----------
  function isEnabled() {
    const v = localStorage.getItem(STORAGE_KEY_ENABLED);
    return v === null ? true : v === "1";
  }

  function syncSettingsUI() {
    const cb = document.getElementById('spa-enabled-toggle');
    if (cb) cb.checked = isEnabled();
  }

  function setEnabled(enabled) {
    localStorage.setItem(STORAGE_KEY_ENABLED, enabled ? "1" : "0");

    // 如未挂载且要启用，则先挂载
    if (enabled && !document.getElementById('spa-fab')) {
      mountUI();
    }

    const fab = document.getElementById('spa-fab');
    const overlay = document.getElementById(SPA_OVERLAY_ID);

    if (enabled) {
      if (fab) fab.style.display = 'block';
      if (overlay) overlay.style.display = 'none'; // 不自动弹出
    } else {
      if (fab) fab.style.display = 'none';
      if (overlay) overlay.style.display = 'none';
    }

    syncSettingsUI();
  }

  // ---------- 外部资源加载（momo风格） ----------
  async function spaLoadExternalAssets() {
    // 1) 样式（可多次刷新避免缓存）
    if (!document.querySelector(`link[data-spa-style="1"]`)) {
      const link = document.createElement("link");
      link.rel = "stylesheet";
      link.type = "text/css";
      link.href = `${SPA_EXT_PATH}/style.css?v=${Date.now()}`;
      link.setAttribute("data-spa-style", "1");
      document.head.appendChild(link);
    }

    // 2) settings.html 插入 ST 设置面板
    if (!document.getElementById(SPA_SETTINGS_MOUNT_ID)) {
      try {
        const settingsHtml = await $.get(`${SPA_EXT_PATH}/settings.html?v=${Date.now()}`);
        $("#extensions_settings2").append(settingsHtml);
      } catch (err) {
        console.error("[SPA] 加载 settings.html 失败：", err);
      }
    }

    // 3) overlay.html（可选）：如存在，就外置化 overlay；否则走内建
    if (!document.getElementById(SPA_OVERLAY_ID)) {
      try {
        const overlayHtml = await $.get(`${SPA_EXT_PATH}/overlay.html?v=${Date.now()}`);
        $("body").append(overlayHtml);
      } catch (err) {
        // overlay.html 可不存在，忽略，稍后 mountUI 内建
      }
    }
  }

  // ---------- 拖拽 ----------
    // ---------- 拖拽与定位 (采用新的、更可靠的逻辑) ----------

  // -- 新增: 整合了点击与拖拽的统一处理器 --
  function addDragAndClickHandler(el, onClick) {
    let isClick = true;

    const dragHandler = (e) => {
      if (e.type === 'touchstart') e.preventDefault();
      
      const rect = el.getBoundingClientRect();
      const offsetX = (e.type.includes('mouse') ? e.clientX : e.touches[0].clientX) - rect.left;
      const offsetY = (e.type.includes('mouse') ? e.clientY : e.touches[0].clientY) - rect.top;

      const move = (moveEvent) => {
        isClick = false; // 只要移动了就不是点击
        el.style.cursor = 'grabbing';

        let newLeft = (moveEvent.type.includes('mouse') ? moveEvent.clientX : moveEvent.touches[0].clientX) - offsetX;
        let newTop = (moveEvent.type.includes('mouse') ? moveEvent.clientY : moveEvent.touches[0].clientY) - offsetY;

        // 使用 Math.max/min 进行边界限制，更简洁高效
        newLeft = Math.max(0, Math.min(newLeft, window.innerWidth - el.offsetWidth));
        newTop = Math.max(0, Math.min(newTop, window.innerHeight - el.offsetHeight));

        el.style.left = `${newLeft}px`;
        el.style.top = `${newTop}px`;
        el.style.right = 'auto';
        el.style.bottom = 'auto';
      };

      const end = () => {
        document.removeEventListener('mousemove', move);
        document.removeEventListener('mouseup', end);
        document.removeEventListener('touchmove', move);
        document.removeEventListener('touchend', end);
        el.style.cursor = 'grab';

        if (isClick) {
          // 如果是点击，则执行传入的点击回调
          onClick();
        } else {
          // 如果是拖拽，则保存位置
          localStorage.setItem(STORAGE_KEY_POS, JSON.stringify({
            top: el.style.top,
            left: el.style.left,
          }));
        }
        // 重置 isClick 状态
        setTimeout(() => { isClick = true; }, 0);
      };

      document.addEventListener('mousemove', move);
      document.addEventListener('mouseup', end);
      document.addEventListener('touchmove', move, { passive: false });
      document.addEventListener('touchend', end);
    };

    el.addEventListener('mousedown', dragHandler);
    el.addEventListener('touchstart', dragHandler, { passive: false });
  }

  // -- 新增: 简洁的位置恢复函数 --
  function restoreFabPosition(el) {
    const savedPos = JSON.parse(localStorage.getItem(STORAGE_KEY_POS));
    if (savedPos?.top && savedPos?.left) {
      el.style.position = 'fixed'; // 确保 position 属性正确
      el.style.top = savedPos.top;
      el.style.left = savedPos.left;
      el.style.right = 'auto';
      el.style.bottom = 'auto';
    } else {
      // 如果没有保存的位置，则使用CSS的默认值 (right: 20px, bottom: 20px)
      // 此处无需额外代码，因为元素创建时就会应用CSS样式
    }
  }

  // ---------- 内页（iframe）HTML ----------
  // -- 修改开始: 移除 head, style, 和外层 html/body 标签 --
  const RAW_HTML = `
  <div class="card" id="spa-iframe-content">
    <div class="header">
      <h1>预设打包助手</h1>
      <p>预设、正则、快速回复与世界书一键打包/导入</p>
    </div>

    <div class="content">
      <div class="mode-tabs">
        <button class="tab-btn active" onclick="switchTab('author')">打包</button>
        <button class="tab-btn" onclick="switchTab('player')">导入</button>
      </div>

      <div id="author-tab" class="tab-content active">
        <div class="main-layout">
          <div class="section resource-section">
            <div class="section-title">选择要打包的资源</div>
            <div class="selection-tabs">
              <button id="select-tab-presets" class="selection-tab-btn active" onclick="switchSelectionTab('presets')">预设</button>
              <button id="select-tab-regexes" class="selection-tab-btn" onclick="switchSelectionTab('regexes')">正则</button>
              <button id="select-tab-quick-replies" class="selection-tab-btn" onclick="switchSelectionTab('quick-replies')">快速回复</button>
              <button id="select-tab-worldbooks" class="selection-tab-btn" onclick="switchSelectionTab('worldbooks')">世界书</button>
            </div>
            <div id="selection-content-presets" class="selection-content active">
              <div id="presets-list" class="list-box"><div class="empty-state">加载中...</div></div>
            </div>
            <div id="selection-content-regexes" class="selection-content">
              <div id="regexes-list" class="list-box"><div class="empty-state">加载中...</div></div>
            </div>
            <div id="selection-content-quick-replies" class="selection-content">
              <div id="quick-reply-list" class="list-box"><div class="empty-state">加载中...</div></div>
            </div>
            <div id="selection-content-worldbooks" class="selection-content">
              <div id="worldbooks-list" class="list-box"><div class="empty-state">加载中...</div></div>
            </div>
          </div>

          <div class="section config-section">
            <div class="section-title">打包配置</div>
            <div class="input-group">
              <input type="text" id="tag-prefix" class="input-field" placeholder="标签前缀(可选)" style="flex:1;">
              <input type="text" id="package-name" class="input-field" placeholder="打包文件名" style="flex:2;">
            </div>
            <div class="config-options">
              <div class="option-group">
                <div class="option-title">正则选项</div>
                <div class="checkbox-option">
                  <input type="checkbox" id="regex-no-overwrite">
                  <label for="regex-no-overwrite">正则禁覆盖(添加“_新”后缀)</label>
                </div>
              </div>
            </div>
            <button class="btn btn-success" onclick="createPackage()">确定并生成打包文件</button>
          </div>
        </div>
      </div>

      <div id="player-tab" class="tab-content">
        <div class="section">
          <div class="section-title">文件导入</div>
          <label class="file-upload">
            <input type="file" id="package-file" accept=".json" onchange="handleFileSelect(event)">
            选择打包文件
          </label>
          <div id="package-info" class="package-info">
            <h4>包信息</h4>
            <div id="package-details"></div>
            <button class="btn btn-success" onclick="importPackage()" style="margin-top:10px;">导入到酒馆</button>
          </div>
        </div>
      </div>

      <div class="progress" id="progress"><div class="progress-bar" id="progress-bar"></div></div>
      <div class="status-msg" id="status-msg"></div>
      <div class="debug-log" id="debug-log"></div>
    </div>
  </div>

<script>
  let selectedPresets = [];
  let selectedRegexes = [];
  let selectedQuickReplySets = [];
  let selectedWorldbooks = [];
  let packageData = null;

  function debugLog(msg){
    console.log('[打包助手]', msg);
    const el = document.getElementById('debug-log');
    if (!el) return;
    el.style.display = 'block';
    el.innerHTML += new Date().toLocaleTimeString() + ': ' + msg + '<br>';
    el.scrollTop = el.scrollHeight;
  }
  function showStatus(message, type='info'){
    const el = document.getElementById('status-msg');
    el.textContent = message;
    el.className = 'status-msg status-' + type;
    el.style.display = 'block';
    setTimeout(()=>{ el.style.display='none'; }, 4000);
    debugLog('状态: ' + type.toUpperCase() + ' - ' + message);
  }
  function showProgress(p){ const k=document.getElementById('progress'); const b=document.getElementById('progress-bar'); k.style.display='block'; b.style.width=p+'%'; if(p>=100){ setTimeout(()=>{k.style.display='none';},800);} }

  function switchTab(tab){
    document.querySelectorAll('.tab-btn').forEach(b=>b.classList.remove('active'));
    document.querySelectorAll('.tab-content').forEach(c=>c.classList.remove('active'));
    document.querySelector('[onclick="switchTab(\\''+tab+'\\')"]').classList.add('active');
    document.getElementById(tab+'-tab').classList.add('active');
    const d=document.getElementById('debug-log'); if(d){ d.style.display='none'; d.innerHTML=''; }
  }
  function switchSelectionTab(tab){
    document.querySelectorAll('.selection-tab-btn').forEach(b=>b.classList.remove('active'));
    document.querySelectorAll('.selection-content').forEach(c=>c.classList.remove('active'));
    document.getElementById('select-tab-'+tab).classList.add('active');
    document.getElementById('selection-content-'+tab).classList.add('active');
  }

  async function loadPresets(){
    try{
      if(typeof TavernHelper==='undefined') throw new Error('需要在 SillyTavern 环境中运行');
      const names = TavernHelper.getPresetNames();
      const box = document.getElementById('presets-list'); box.innerHTML='';
      if(!names || names.length===0){ box.innerHTML='<div class="empty-state">未找到预设</div>'; return; }
      for(const name of names){
        if(name==='in_use') continue;
        const preset = TavernHelper.getPreset(name);
        if(!preset) continue;
        const row = document.createElement('div'); row.className='item-row';
        row.innerHTML = '<input type="checkbox" id="preset-'+name+'" onchange="togglePreset(\\''+name+'\\')">'+
                        '<div><div class="item-name">'+name+'</div>'+
                        '<div class="item-desc">'+(preset.prompts?preset.prompts.length:0)+' 个提示词</div></div>';
        box.appendChild(row);
      }
    }catch(e){
      document.getElementById('presets-list').innerHTML='<div class="empty-state">加载失败: '+e.message+'</div>';
      debugLog('预设加载错误: '+e.stack);
    }
  }

  async function loadRegexes(){
    try{
      if(typeof TavernHelper==='undefined') throw new Error('需要在 SillyTavern 环境中运行');
      const regexes = TavernHelper.getTavernRegexes() || [];
      const box = document.getElementById('regexes-list'); box.innerHTML='';
      if(regexes.length===0){ box.innerHTML='<div class="empty-state">未找到正则</div>'; return; }
      for(const r of regexes){
        const row = document.createElement('div'); row.className='item-row';
        const pv = (r.find_regex||'').toString().slice(0,30);
        row.innerHTML = '<input type="checkbox" id="regex-'+r.id+'" onchange="toggleRegex(\\''+r.id+'\\')">'+
                        '<div><div class="item-name">'+r.script_name+'</div>'+
                        '<div class="item-desc">'+pv+'...</div></div>';
        box.appendChild(row);
      }
    }catch(e){
      document.getElementById('regexes-list').innerHTML='<div class="empty-state">加载失败: '+e.message+'</div>';
      debugLog('正则加载错误: '+e.stack);
    }
  }

  async function loadQuickReplies(){
    try{
      if(typeof TavernHelper==='undefined') throw new Error('需要在 SillyTavern 环境中运行');
      const setListJson = await TavernHelper.triggerSlash('/qr-set-list all');
      const setNames = JSON.parse(setListJson) || [];
      const box = document.getElementById('quick-reply-list'); box.innerHTML='';
      if(setNames.length===0){ box.innerHTML='<div class="empty-state">未找到快速回复集</div>'; return; }
      for(const name of setNames){
        const listJson = await TavernHelper.triggerSlash('/qr-list "'+name+'"');
        const arr = JSON.parse(listJson) || [];
        const row = document.createElement('div'); row.className='item-row';
        row.innerHTML = '<input type="checkbox" id="qrset-'+name+'" onchange="toggleQuickReplySet(\\''+name+'\\')">'+
                        '<div><div class="item-name">'+name+'</div>'+
                        '<div class="item-desc">'+arr.length+' 个回复</div></div>';
        box.appendChild(row);
      }
    }catch(e){
      document.getElementById('quick-reply-list').innerHTML='<div class="empty-state">加载失败: '+e.message+'</div>';
      debugLog('快速回复加载错误: '+e.stack);
    }
  }

  async function loadWorldbooks(){
    const box = document.getElementById('worldbooks-list');
    try{
      if(typeof TavernHelper==='undefined') throw new Error('需要在 SillyTavern 环境中运行');
      if(typeof TavernHelper.getWorldbookNames!=='function'||typeof TavernHelper.getWorldbook!=='function'){
        box.innerHTML = '<div class="empty-state">当前酒馆助手版本不支持世界书 API，请更新</div>';
        debugLog('世界书API不可用：缺少 getWorldbookNames/getWorldbook');
        return;
      }
      const names = TavernHelper.getWorldbookNames() || [];
      box.innerHTML='';
      if(names.length===0){ box.innerHTML='<div class="empty-state">未找到世界书</div>'; return; }
      for(const name of names){
        let cnt='...';
        try{ const entries=await TavernHelper.getWorldbook(name); cnt=(Array.isArray(entries)?entries.length:0)+' 条目'; }catch{}
        const row=document.createElement('div'); row.className='item-row';
        row.innerHTML = '<input type="checkbox" id="worldbook-'+name+'" onchange="toggleWorldbook(\\''+name+'\\')">'+
                        '<div><div class="item-name">'+name+'</div>'+
                        '<div class="item-desc">'+cnt+'</div></div>';
        box.appendChild(row);
      }
    }catch(e){
      box.innerHTML='<div class="empty-state">加载失败: '+e.message+'</div>';
      debugLog('世界书加载错误: '+e.stack);
    }
  }

  function togglePreset(name){ selectedPresets = selectedPresets.includes(name)? selectedPresets.filter(n=>n!==name):[...selectedPresets,name]; debugLog('预设选择: '+selectedPresets.length+' 个'); }
  function toggleRegex(id){ selectedRegexes = selectedRegexes.includes(id)? selectedRegexes.filter(n=>n!==id):[...selectedRegexes,id]; debugLog('正则选择: '+selectedRegexes.length+' 个'); }
  function toggleQuickReplySet(name){ selectedQuickReplySets = selectedQuickReplySets.includes(name)? selectedQuickReplySets.filter(n=>n!==name):[...selectedQuickReplySets,name]; debugLog('快速回复集选择: '+selectedQuickReplySets.length+' 个'); }
  function toggleWorldbook(name){ selectedWorldbooks = selectedWorldbooks.includes(name)? selectedWorldbooks.filter(n=>n!==name):[...selectedWorldbooks,name]; debugLog('世界书选择: '+selectedWorldbooks.length+' 本'); }

  async function createPackage(){
    if(selectedPresets.length===0 && selectedRegexes.length===0 && selectedQuickReplySets.length===0 && selectedWorldbooks.length===0){
      showStatus('请至少选择一个项目','error'); return;
    }
    const packageName = document.getElementById('package-name').value.trim();
    if(!packageName){ showStatus('请输入包名称','error'); return; }
    const tagPrefix = document.getElementById('tag-prefix').value.trim();
    const regexNoOverwrite = document.getElementById('regex-no-overwrite').checked;

    try{
      showStatus('生成打包文件...','info'); showProgress(10);
      const pkg = {
        name: packageName,
        created: new Date().toISOString(),
        tag_prefix: tagPrefix,
        presets: {},
        regexes: {},
        quick_reply_sets: {},
        worldbooks: {}
      };

      // 预设
      for(const n of selectedPresets){
        const p = TavernHelper.getPreset(n); if(!p) continue;
        const final = tagPrefix? (tagPrefix+n): n;
        pkg.presets[final] = p;
        debugLog('已打包预设: '+final);
      }
      showProgress(30);

      // 正则
      const all = TavernHelper.getTavernRegexes() || [];
      for(const id of selectedRegexes){
        const r = all.find(x=>x.id===id); if(!r) continue;
        let final = r.script_name;
        if(tagPrefix) final = tagPrefix + final;
        if(regexNoOverwrite) final = final + '_新';
        const copy = { ...r, script_name: final };
        delete copy.id;
        pkg.regexes[final] = copy;
        debugLog('已打包正则: '+final);
      }
      showProgress(55);

      // 快速回复
      for(const setName of selectedQuickReplySets){
        const finalSet = tagPrefix? (tagPrefix+setName): setName;
        const listJson = await TavernHelper.triggerSlash('/qr-list "'+setName+'"');
        const labels = JSON.parse(listJson) || [];
        const arr = [];
        for(const label of labels){
          const dataJson = await TavernHelper.triggerSlash('/qr-get set="'+setName+'" label="'+label+'"');
          const data = JSON.parse(dataJson);
          arr.push(data);
        }
        pkg.quick_reply_sets[finalSet] = arr;
        debugLog('已打包快速回复集: '+finalSet);
      }
      showProgress(75);

      // 世界书
      if(selectedWorldbooks.length>0){
        if(typeof TavernHelper.getWorldbook!=='function') throw new Error('当前酒馆助手不支持获取世界书');
        for(const wb of selectedWorldbooks){
          const finalWb = tagPrefix? (tagPrefix+wb): wb;
          const entries = await TavernHelper.getWorldbook(wb);
          pkg.worldbooks[finalWb] = entries || [];
          debugLog('已打包世界书: '+finalWb+'（'+(entries?entries.length:0)+' 条目）');
        }
      }
      showProgress(90);

      const jsonStr = JSON.stringify(pkg, null, 2);
      const blob = new Blob([jsonStr], { type: 'application/json' });
      const url = URL.createObjectURL(blob);
      const a = document.createElement('a'); a.href = url; a.download = packageName + '.json';
      document.body.appendChild(a); a.click(); document.body.removeChild(a);
      URL.revokeObjectURL(url);

      showProgress(100); showStatus('打包完成','success');
      debugLog('打包完成: 预设'+Object.keys(pkg.presets).length+'、正则'+Object.keys(pkg.regexes).length+'、快速回复'+Object.keys(pkg.quick_reply_sets).length+'、世界书'+Object.keys(pkg.worldbooks).length);
    }catch(e){
      showProgress(100); showStatus('打包失败: '+e.message,'error'); debugLog('打包错误: '+e.stack);
    }
  }

  function handleFileSelect(ev){
    const file = ev.target.files[0]; if(!file){ debugLog('未选择文件'); return; }
    ev.target.value = '';
    const reader = new FileReader();
    reader.onload = (e)=>{
      try{
        packageData = JSON.parse(e.target.result);
        debugLog('文件解析成功');
        displayPackageInfo(packageData);
      }catch(err){
        showStatus('文件格式错误','error'); debugLog('解析错误: '+err.message);
        packageData = null; document.getElementById('package-info').style.display='none';
      }
    };
    reader.onerror = ()=>{ showStatus('文件读取失败','error'); debugLog('读取错误'); };
    reader.readAsText(file);
  }

  function displayPackageInfo(data){
    const el = document.getElementById('package-details');
    let html = '<p><strong>名称:</strong> '+(data.name||'未知')+'</p>';
    html += '<p><strong>创建:</strong> '+(data.created? new Date(data.created).toLocaleString():'未知')+'</p>';
    if(data.tag_prefix) html += '<p><strong>标签:</strong> '+data.tag_prefix+'</p>';
    html += '<p><strong>预设:</strong> '+(data.presets?Object.keys(data.presets).length:0)+' 个</p>';
    html += '<p><strong>正则:</strong> '+(data.regexes?Object.keys(data.regexes).length:0)+' 个</p>';
    html += '<p><strong>快速回复集:</strong> '+(data.quick_reply_sets?Object.keys(data.quick_reply_sets).length:0)+' 个</p>';
    html += '<p><strong>世界书:</strong> '+(data.worldbooks?Object.keys(data.worldbooks).length:0)+' 本</p>';
    el.innerHTML = html;
    document.getElementById('package-info').style.display='block';
  }

  async function importPackage(){
    if(!packageData){ showStatus('请先选择文件','error'); return; }
    try{
      showStatus('导入中...','info');

      // 预设
      if(packageData.presets){
        for(const [name, preset] of Object.entries(packageData.presets)){
          if(typeof TavernHelper.createOrReplacePreset==='function'){
            await TavernHelper.createOrReplacePreset(name, preset);
          }
          debugLog('预设导入: '+name);
        }
      }

      // 正则
      if(packageData.regexes && Object.keys(packageData.regexes).length>0){
        const curr = TavernHelper.getTavernRegexes() || [];
        const next = [...curr];
        for(const [name, regex] of Object.entries(packageData.regexes)){
          const rec = { ...regex };
          const idx = next.findIndex(x=>x.script_name===rec.script_name);
          if(idx>=0){
            rec.id = next[idx].id;
            next[idx] = rec;
            debugLog('正则替换: '+rec.script_name);
          }else{
            rec.id = Date.now().toString() + Math.random().toString(36).slice(2,9);
            next.push(rec);
            debugLog('正则新增: '+rec.script_name);
          }
        }
        await TavernHelper.replaceTavernRegexes(next, { scope: 'all' });
      }

      // 快速回复
      if(packageData.quick_reply_sets){
        for(const [setName, arr] of Object.entries(packageData.quick_reply_sets)){
          await TavernHelper.triggerSlash('/qr-set-create "'+setName+'"');
          for(const item of arr){
            const args = Object.entries(item)
              .filter(([k,v])=>k!=='command' && v!==null && v!==undefined)
              .map(([k,v])=> k+'='+JSON.stringify(v))
              .join(' ');
            const cmd = item.command || '';
            await TavernHelper.triggerSlash('/qr-create set="'+setName+'" '+args+' '+cmd);
          }
          debugLog('快速回复集导入: '+setName);
        }
      }

      // 世界书
      if(packageData.worldbooks){
        if(typeof TavernHelper.createOrReplaceWorldbook!=='function') throw new Error('当前酒馆助手不支持创建/替换世界书');
        for(const [wbName, entries] of Object.entries(packageData.worldbooks)){
          await TavernHelper.createOrReplaceWorldbook(wbName, Array.isArray(entries)?entries:[]);
          debugLog('世界书导入: '+wbName+'（'+(entries?entries.length:0)+' 条目）');
        }
      }

      showProgress(100); showStatus('导入完成','success'); debugLog('导入完成');
      packageData = null; const f = document.getElementById('package-file'); if(f) f.value='';
      document.getElementById('package-info').style.display='none';
    }catch(e){
      showProgress(100); showStatus('导入失败: '+e.message,'error'); debugLog('导入错误: '+e.stack);
    }
  }

  async function loadAllResources(){
    showStatus('正在加载所有资源...','info'); showProgress(10);
    await loadPresets();      showProgress(35);
    await loadRegexes();      showProgress(60);
    await loadQuickReplies(); showProgress(85);
    await loadWorldbooks();   showProgress(100);
    showStatus('资源加载完成','success');
  }

</script>`;

  // ---------- UI 装配 ----------
  function mountUI() {
    let fab = document.getElementById('spa-fab');
    if (!fab) {
      fab = document.createElement('div');
      fab.id = 'spa-fab';
      fab.title = '预设打包助手';
      fab.textContent = '📦';
      document.body.appendChild(fab);

      // 定义点击时要执行的动作
      const fabClickAction = () => {
        const ov = ensureOverlay();
        ov.style.display = 'block';

        const loadedFlag = document.body.getAttribute('data-spa-loaded');
        if (!loadedFlag && typeof loadAllResources === 'function') {
          loadAllResources();
          document.body.setAttribute('data-spa-loaded', 'true');
        }
      };
      
      // 使用新的统一处理器来绑定拖拽和点击事件
      addDragAndClickHandler(fab, fabClickAction);

      // 恢复上次的位置
      restoreFabPosition(fab);
    }

    // 绑定 overlay 的关闭事件 (这部分逻辑不变)
    const overlay = ensureOverlay();
    overlay.addEventListener('click', (e) => { if (e.target === overlay) overlay.style.display = 'none'; });
    const closeBtn = overlay.querySelector('#spa-close');
    if (closeBtn && !closeBtn.dataset.listenerAttached) {
      closeBtn.addEventListener('click', () => overlay.style.display = 'none');
      closeBtn.dataset.listenerAttached = 'true'; // 防止重复绑定
    }

    // 注入面板内容 (这部分逻辑不变)
    const contentContainer = overlay.querySelector('#spa-content-container');
    if (contentContainer && !contentContainer.innerHTML) {
      contentContainer.innerHTML = RAW_HTML;
      
      const scriptTag = contentContainer.querySelector('script');
      if (scriptTag) {
        const finalScript = document.createElement('script');
        finalScript.textContent = scriptTag.innerText;
        document.body.appendChild(finalScript);
        scriptTag.remove();
      }
    }
  }
  // 若不存在 overlay，则内建一个；若已通过 overlay.html 加载，则直接返回
  function ensureOverlay() {
    let overlay = document.getElementById(SPA_OVERLAY_ID);
    if (overlay) return overlay;

    overlay = document.createElement('div');
    overlay.id = SPA_OVERLAY_ID;
    overlay.style.display = 'none';
    overlay.innerHTML = `
      <div id="spa-modal">
        <div id="spa-card">
          <button id="spa-close" aria-label="close">×</button>
          <div id="spa-content-container" style="width:100%; height:100%; overflow:auto;"></div>
        </div>
      </div>`;
    document.body.appendChild(overlay);
    return overlay;
  }

  // ---------- 绑定设置页事件 ----------
  document.addEventListener('change', (e) => {
    const t = e.target;
    if (t && t.id === 'spa-enabled-toggle') {
      setEnabled(Boolean(t.checked));
    }
  });

  document.addEventListener('click', (e) => {
    const t = e.target;
    if (t && t.id === 'spa-reset-pos-btn') {
      localStorage.removeItem(STORAGE_KEY_POS);
      const fab = document.getElementById('spa-fab');
      if (fab) {
        fab.style.left = '';
        fab.style.top = '';
        fab.style.right = '20px';
        fab.style.bottom = '20px';
      }
    }
  });

  const spaSettingsMO = new MutationObserver(() => syncSettingsUI());
  spaSettingsMO.observe(document.body, { childList: true, subtree: true });

  // ---------- 初始化顺序 ----------
  await spaLoadExternalAssets();     // 先加载 settings.html / style.css / overlay.html(可选)

  if (isEnabled()) {                 // 再按开关决定是否挂载 FAB
    mountUI();
  }
  syncSettingsUI();                  // 同步设置页的勾选状态

  console.log('[st-packaging-assistant] loaded');
});
